/*
 * Copyright (c) 2011, Jordan Taekema
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.andactive;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * To use active records extend this class like
 * <pre>class Person extends ActiveRecord&lt;Person&gt; {...}</pre>
 * <p/>
 * The following fields are added to all objects (and database tables)
 * <ul><li>id (integer, primary key, not null, auto-incremented)</li>
 * <li>created (date, not null)</li>
 * <li>modified (date, not null)</li></ul>
 * <p/>
 * Note: fields which start with "_" or "m_" are not stored in the database.
 * <p/>
 * Created 7/9/11 2:10 AM by Jordan Taekema
 */
@SuppressWarnings({"unchecked", "unused"})
public abstract class ActiveRecord<T extends ActiveRecord> {

	/**
	 * For logging
	 */
	private static final String TAG = ActiveRecord.class.getName();

	/**
	 * Cache the fields so we don't have to do slow reflection more than once per class.
	 */
	private static ConcurrentHashMap<String, Collection<Field>> fieldCache = new ConcurrentHashMap<String, Collection<Field>>();

	/**
	 * The application context.
	 */
	protected Context _context;

	/**
	 * True if the entity has been inserted into the database.
	 */
	private boolean _isInserted;

	/**
	 * Primary key, this field is in every database table, it is auto-generated
	 */
	long id;

	/**
	 * The date and time this entity was created
	 */
	@Column(nullable = false)
	private Date created;

	/**
	 * The date and time this entity was last modified (and saved to the database)
	 */
	@Column(nullable = false)
	private Date modified;

	/**
	 * Constructor
	 *
	 * @param context the application context for the current process
	 */
	public ActiveRecord(Context context) {
		_context = context;
	}

	/**
	 * @return true if this entity exists in the database; false otherwise
	 */
	public boolean isInserted() {
		return _isInserted;
	}

	/**
	 * Returns the primary key of this entity.
	 * <p/>
	 * Returns -1 if it has not yet been saved in the database.
	 *
	 * @return id of this record if it has been saved in the database; -1 otherwise
	 */
	public long getId() {
		return (_isInserted && id != 0) ? id : -1;
	}

	/**
	 * Returns the date this entity was created.
	 *
	 * @return date this entity was created
	 */
	public Date getCreated() {
		return created;
	}

	/**
	 * Returns the date this entity was last modified.
	 *
	 * @return date this entity was last modified
	 */
	public Date getModified() {
		return modified;
	}

	public Class<T> getClassType() {
		return (Class<T>) this.getClass();
	}

	/**
	 * Indicates whether some other object is "equal to" this one.
	 *
	 * @param obj the reference object with which to compare.
	 * @return true if this object is the same as the obj argument; false otherwise.
	 */
	@Override
	public boolean equals(Object obj) {
		if (!ActiveRecord.class.isAssignableFrom(obj.getClass())) {
			return false;
		}
		ActiveRecord<T> entity = (ActiveRecord<T>) obj;

		if (this._isInserted || entity._isInserted)
			// if either has been inserted into the database, just match the id's
			return this.getId() == entity.getId();
		else
			// or else match the objects
			return this == entity;
	}


	/**
	 * Deletes this entity from the database.
	 *
	 * @throws ActiveRecordException when an error happens
	 */
	public void delete() {
		if (!_isInserted) return;

		delete(_context, getClassType(), id);
		id = 0;

		_isInserted = false;
	}

	/**
	 * Saves this entity to the database, inserts or updates as needed.
	 */
	public void save() {
		if (!_isInserted) {
			insert();
		} else {
			update();
		}
		// no need to touch the database
	}

	/**
	 * Query's the database
	 *
	 * @param sql  a query to run in the database, formatted as an SQL statement.
	 * @param args you may include ?s in sql, which will be replaced by the values from args, in the order that they
	 *             appear in sql. The values will be bound as Strings.
	 * @return a generic Collection of all entities matching the query
	 */
	public Collection<T> query(String sql, String[] args) {
		return query(_context, getClassType(), sql, args);
	}

	/**
	 * This is a the reverse of a "one to many" relationship.
	 *
	 * @param type	   the type of active record to return
	 * @param foreignKey the name of the variable in the other class that references
	 * @param <T>        any class that extends from the ActiveRecord class
	 * @return all of the specified entities that reference this object
	 */
	protected <T extends ActiveRecord> Collection<T> getMany(Class<T> type, String foreignKey) {
		if (!_isInserted)
			return null;

		return selectAll(_context, type, foreignKey + "_id = ?", null);
	}

	/**
	 * Updates this active record in the database
	 */
	private void update() {
		if (!_isInserted || id <= 0)
			throw new ActiveRecordException("can not update a record that has not been inserted");

		update(_context, getClassType(), (T) this);
	}

	/**
	 * Inserts this active record into the database
	 */
	private void insert() {
		insert(_context, getClassType(), (T) this);
	}

	/**
	 * @param context the application context for the current process
	 * @param type	the active record class of the entity to save
	 * @param entity  the entity to save
	 * @param <T>     any class that extends from the ActiveRecord class
	 */
	public static <T extends ActiveRecord> void save(Context context, Class<T> type, T entity) {
		if (entity.isInserted()) {
			insert(context, type, entity);
		} else {
			update(context, type, entity);
		}
	}

	/**
	 * Saves a collection of active record entities to the database.
	 * <p/>
	 * New entities are inserted and modified entities are updated, however if an entity has not been changes it will
	 * not call update on that entity.
	 *
	 * @param context  the application context for the current process
	 * @param type	 the active record class of the entities to save
	 * @param entities a collection of entities to save to the database
	 * @param <T>      any class that extends from the ActiveRecord class
	 */
	public static <T extends ActiveRecord> void saveAll(Context context, Class<T> type, Collection<T> entities) {
		Collection<T> toUpdate = new HashSet<T>();
		Collection<T> toInsert = new HashSet<T>();
		for (T entity : entities) {
			if (!entity.isInserted()) {
				toInsert.add(entity);
			} else {
				toUpdate.add(entity);
			}
		}
		insertAll(context, type, toInsert);
		updateAll(context, type, toUpdate);
	}

	/**
	 * Deletes an entity from the database.
	 *
	 * @param context the application context for the current process
	 * @param type	the active record class of the entities to delete
	 * @param id	  the primary key of the entity to delete
	 */
	public static void delete(Context context, Class<? extends ActiveRecord> type, long id) {
		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		// remove this from the entity cache
		ActiveRecordCache.remove(type, id);

		db.delete(DatabaseHelper.getTableName(type), "ID = ?", new String[]{Long.toString(id)});
		db.close();
	}

	/**
	 * Deletes all entities from the database.
	 *
	 * @param context the application context for the current process
	 * @param type	the active record class of the entities to delete
	 */
	public static void deleteAll(Context context, Class<? extends ActiveRecord> type) {
		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		// clear the entity cache for all of this type
		ActiveRecordCache.clear(type);

		// an optimization might be to truncate the table instead of "DELETE FROM table"
		db.delete(DatabaseHelper.getTableName(type), null, null);
		db.close();
	}

	/**
	 * Deletes a collection of entities from the database.
	 *
	 * @param context  the application context for the current process
	 * @param type	 the active record class of the entities to delete
	 * @param entities a collection of entities to delete
	 * @param <T>      any class that extends from the ActiveRecord class
	 */
	public static <T extends ActiveRecord> void deleteAll(Context context, Class<T> type, Collection<T> entities) {
		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		StringBuilder sbWhere = new StringBuilder("id IN (");
		boolean first = true;
		for (T entity : entities) {
			if (entity.isInserted()) {
				// clear the entity cache
				ActiveRecordCache.remove(type, entity.id);
				sbWhere.append(Long.toString(entity.id));
				entity.id = 0;
				if (!first)
					sbWhere.append(",");
				else
					first = false;
			}
		}
		sbWhere.append(")");

		// an optimization might be to truncate the table instead of "DELETE FROM table"
		db.delete(DatabaseHelper.getTableName(type), sbWhere.toString(), null);
		db.close();
	}

	/**
	 * Select an active record entity from the database.
	 *
	 * @param context the application context for the current process
	 * @param type	the active record class of the entities to select
	 * @param id	  the primary key of the entity to select.
	 * @param <T>     any class that extends from the ActiveRecord class
	 * @return the matching entity
	 * @see ActiveRecord#selectAll(android.content.Context, Class, String, String[], String, String, String, String) selectAll
	 */
	public static <T extends ActiveRecord> T select(Context context, Class<T> type, long id) {

		if (id <= 0) {
			throw new IllegalArgumentException("id (" + id + ") should not be 0 or negative");
		}

		// check the cache first
		T entity = ActiveRecordCache.get(type, id);
		if (entity != null) {
			return entity;
		}

		return select(context, type, "ID = ?", new String[]{Long.toString(id)});
	}

	/**
	 * Select an active record entity from the database.
	 *
	 * @param context	   the application context for the current process
	 * @param type		  the active record class of the entities to select
	 * @param selection	 a filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE
	 *                      itself). Passing null will return all rows for the given table.
	 * @param selectionArgs you may include ?s in selection, which will be replaced by the values from selectionArgs, in
	 *                      the order that they appear in the selection. The values will be bound as Strings.
	 * @param <T>           any class that extends from the ActiveRecord class
	 * @return the matching entity
	 * @see ActiveRecord#selectAll(android.content.Context, Class, String, String[], String, String, String, String) selectAll
	 */
	public static <T extends ActiveRecord> T select(Context context, Class<T> type, String selection, String[] selectionArgs) {
		return select(context, type, selection, selectionArgs, null);
	}

	/**
	 * Select an active record entity from the database.
	 *
	 * @param context	   the application context for the current process
	 * @param type		  the active record class of the entities to select
	 * @param selection	 a filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE
	 *                      itself). Passing null will return all rows for the given table.
	 * @param selectionArgs you may include ?s in selection, which will be replaced by the values from selectionArgs, in
	 *                      the order that they appear in the selection. The values will be bound as Strings.
	 * @param orderBy	   how to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself).
	 *                      Passing null will use the default sort order, which may be unordered.
	 * @param <T>           any class that extends from the ActiveRecord class
	 * @return the matching entity
	 * @see ActiveRecord#selectAll(android.content.Context, Class, String, String[], String, String, String, String) selectAll
	 */
	public static <T extends ActiveRecord> T select(Context context, Class<T> type, String selection, String[] selectionArgs, String orderBy) {
		return select(context, type, selection, selectionArgs, null, null, orderBy);
	}

	/**
	 * Select an active record entity from the database.
	 *
	 * @param context	   the application context for the current process
	 * @param type		  the active record class of the entities to select
	 * @param selection	 a filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE
	 *                      itself). Passing null will return all rows for the given table.
	 * @param selectionArgs you may include ?s in selection, which will be replaced by the values from selectionArgs, in
	 *                      the order that they appear in the selection. The values will be bound as Strings.
	 * @param groupBy	   a filter declaring how to group rows, formatted as an SQL GROUP BY clause (excluding the
	 *                      GROUP BY itself). Passing null will cause the rows to not be grouped.
	 * @param having		a filter declare which row groups to include in the cursor, if row grouping is being used,
	 *                      formatted as an SQL HAVING clause (excluding the HAVING itself). Passing null will cause all
	 *                      row groups to be included, and is required when row grouping is not being used.
	 * @param orderBy	   how to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself).
	 *                      Passing null will use the default sort order, which may be unordered.
	 * @param <T>           any class that extends from the ActiveRecord class
	 * @return the matching entity
	 * @see ActiveRecord#selectAll(android.content.Context, Class, String, String[], String, String, String, String) selectAll
	 */
	public static <T extends ActiveRecord> T select(Context context, Class<T> type, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) {
		Collection<T> records = selectAll(context, type, selection, selectionArgs, groupBy, having, orderBy, "1");
		if (records != null && records.size() > 0) {
			return records.iterator().next();
		} else {
			return null;
		}
	}

	/**
	 * Returns a collection of entities from the database.
	 *
	 * @param context the application context for the current process
	 * @param type	the active record class of the entities to select
	 * @param <T>     any class that extends from the ActiveRecord class
	 * @return a generic Collection of all matching entities
	 * @see ActiveRecord#selectAll(android.content.Context, Class, String, String[], String, String, String, String) selectAll
	 */
	public static <T extends ActiveRecord> Collection<T> selectAll(Context context, Class<T> type) {
		return selectAll(context, type, null, null);
	}

	/**
	 * Returns a collection of entities from the database.
	 *
	 * @param context	   the application context for the current process
	 * @param type		  the active record class of the entities to select
	 * @param selection	 a filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE
	 *                      itself). Passing null will return all rows for the given table.
	 * @param selectionArgs you may include ?s in selection, which will be replaced by the values from selectionArgs, in
	 *                      the order that they appear in the selection. The values will be bound as Strings.
	 * @param <T>           any class that extends from the ActiveRecord class
	 * @return a generic Collection of all matching entities
	 * @see ActiveRecord#selectAll(android.content.Context, Class, String, String[], String, String, String, String) selectAll
	 */
	public static <T extends ActiveRecord> Collection<T> selectAll(Context context, Class<T> type, String selection, String[] selectionArgs) {
		return selectAll(context, type, selection, selectionArgs, null);
	}

	/**
	 * Returns a collection of entities from the database.
	 *
	 * @param context	   the application context for the current process
	 * @param type		  the active record class of the entities to select
	 * @param selection	 a filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE
	 *                      itself). Passing null will return all rows for the given table.
	 * @param selectionArgs you may include ?s in selection, which will be replaced by the values from selectionArgs, in
	 *                      the order that they appear in the selection. The values will be bound as Strings.
	 * @param orderBy	   how to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself).
	 *                      Passing null will use the default sort order, which may be unordered.
	 * @param <T>           any class that extends from the ActiveRecord class
	 * @return a generic Collection of all matching entities
	 * @see ActiveRecord#selectAll(android.content.Context, Class, String, String[], String, String, String, String) selectAll
	 */
	public static <T extends ActiveRecord> Collection<T> selectAll(Context context, Class<T> type, String selection, String[] selectionArgs, String orderBy) {
		return selectAll(context, type, selection, selectionArgs, orderBy, null);
	}

	/**
	 * Returns a collection of entities from the database.
	 *
	 * @param context	   the application context for the current process
	 * @param type		  the active record class of the entities to select
	 * @param selection	 a filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE
	 *                      itself). Passing null will return all rows for the given table.
	 * @param selectionArgs you may include ?s in selection, which will be replaced by the values from selectionArgs, in
	 *                      the order that they appear in the selection. The values will be bound as Strings.
	 * @param orderBy	   how to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself).
	 *                      Passing null will use the default sort order, which may be unordered.
	 * @param limit		 limits the number of rows returned by the query, formatted as LIMIT clause (excluding the LIMIT
	 *                      itself).  To offset the result set you may also specify an offset with the format
	 *                      "offset,limit", passing null denotes no LIMIT clause.
	 * @param <T>           any class that extends from the ActiveRecord class
	 * @return a generic Collection of all matching entities
	 * @see ActiveRecord#selectAll(android.content.Context, Class, String, String[], String, String, String, String) selectAll
	 */
	public static <T extends ActiveRecord> Collection<T> selectAll(Context context, Class<T> type, String selection, String[] selectionArgs, String orderBy, String limit) {
		return selectAll(context, type, selection, selectionArgs, null, null, orderBy, limit);
	}

	/**
	 * Returns a collection of entities from the database.
	 *
	 * @param context	   the application context for the current process
	 * @param type		  the active record class of the entities to select
	 * @param selection	 a filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE
	 *                      itself). Passing null will return all rows for the given table.
	 * @param selectionArgs you may include ?s in selection, which will be replaced by the values from selectionArgs, in
	 *                      the order that they appear in the selection. The values will be bound as Strings.
	 * @param groupBy	   a filter declaring how to group rows, formatted as an SQL GROUP BY clause (excluding the
	 *                      GROUP BY itself). Passing null will cause the rows to not be grouped.
	 * @param having		a filter declare which row groups to include in the cursor, if row grouping is being used,
	 *                      formatted as an SQL HAVING clause (excluding the HAVING itself). Passing null will cause all
	 *                      row groups to be included, and is required when row grouping is not being used.
	 * @param orderBy	   how to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself).
	 *                      Passing null will use the default sort order, which may be unordered.
	 * @param limit		 limits the number of rows returned by the query, formatted as LIMIT clause (excluding the LIMIT
	 *                      itself).  To offset the result set you may also specify an offset with the format
	 *                      "offset,limit", passing null denotes no LIMIT clause.
	 * @param <T>           any class that extends from the ActiveRecord class
	 * @return a generic Collection of all matching entities
	 */
	public static <T extends ActiveRecord> Collection<T> selectAll(Context context, Class<T> type, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		String tableName = DatabaseHelper.getTableName(type);

		ArrayList<String> columns = new ArrayList<String>();
		for (Field field : getFields(type)) {
			columns.add(DatabaseHelper.getColumnName(field));
		}
		String[] columnNames = columns.toArray(new String[columns.size()]);

		Collection<T> entities = getActiveRecords(context, type, db.query(false, tableName, columnNames, selection, selectionArgs, groupBy, having, orderBy, limit));

		// don't forget to close the database after we are done using the cursor
		db.close();

		return entities;
	}

	/**
	 * Query the database.
	 *
	 * @param context  the application context for the current process
	 * @param type	 the active record class of the entities returned from the query
	 * @param sql	  a query to run in the database, formatted as an SQL statement.
	 * @param bindArgs you may include ?s in sql, which will be replaced by the values from args, in the order that they
	 *                 appear in sql. The values will be bound as Strings.
	 * @param <T>      any class that extends from the ActiveRecord class
	 * @return a generic Collection of all entities matching the query
	 * @throws ActiveRecordException
	 */
	public static <T extends ActiveRecord> Collection<T> query(Context context, Class<T> type, String sql, String[] bindArgs) {
		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		Collection<T> entities = getActiveRecords(context, type, db.rawQuery(sql, bindArgs));
		db.close();

		return entities;
	}

	public static void execute(Context context, Class<? extends ActiveRecord> type, String sql, String[] bindArgs) {
		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		db.execSQL(sql, bindArgs);
		db.close();
	}

	private static <T extends ActiveRecord> void insert(Context context, Class<T> type, T entity) {
		if (entity._isInserted || entity.id > 0)
			throw new ActiveRecordException("Can not insert a record that has already been inserted");

		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		// set the created and modified date
		entity.created = new Date();
		entity.modified = new Date();

		Log.w(TAG, "insert into " + DatabaseHelper.getTableName(type));
		entity.id = db.insert(DatabaseHelper.getTableName(type), null, getValues(type, entity));

		db.close();

		// if it was inserted sucessfully
		if (entity.id > 0) {
			entity._isInserted = true;

			// update the cache with this entity
			ActiveRecordCache.put(type, entity);
		} else {
			throw new ActiveRecordException("problem inserting active record");
		}
	}

	private static <T extends ActiveRecord> void insertAll(Context context, Class<T> type, Collection<T> entities) {
		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		// I suppose this should be faster than doing it individually with auto-commit on
		db.beginTransaction();
		for (T entity : entities) {
			if (entity._isInserted || entity.id > 0)
				throw new ActiveRecordException("Can not insert a record that has already been inserted");

			// set the created and modified date
			entity.created = new Date();
			entity.modified = new Date();

			entity.id = db.insert(DatabaseHelper.getTableName(type), null, getValues(type, entity));
			if (entity.id > 0) {
				entity._isInserted = true;

				// update the cache with this entity
				ActiveRecordCache.put(type, entity);
			}
		}
		db.setTransactionSuccessful();
		db.endTransaction();
		db.close();
	}

	private static <T extends ActiveRecord> void update(Context context, Class<T> type, T entity) {
		if (entity.getId() <= 0)
			throw new ActiveRecordException("Can not update an active record without an id");

		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		// update the modified date
		entity.modified = new Date();

		db.update(DatabaseHelper.getTableName(type), getValues(type, entity), "ID = ?", new String[]{Long.toString(entity.getId())});
		db.close();
	}

	private static <T extends ActiveRecord> void updateAll(Context context, Class<T> type, Collection<T> entities) {

		DatabaseHelper helper = new DatabaseHelper(context);
		SQLiteDatabase db = helper.getWritableDatabase(type);

		// I suppose this should be faster than doing it individually with auto-commit on
		db.beginTransaction();
		for (T entity : entities) {
			if (entity.getId() <= 0)
				throw new ActiveRecordException("Can not update an active record without an id");

			// update the modified date
			entity.modified = new Date();

			db.update(DatabaseHelper.getTableName(type), getValues(type, entity), "ID = ?", new String[]{Long.toString(entity.getId())});
		}
		db.setTransactionSuccessful();
		db.endTransaction();
		db.close();
	}

	private static <T extends ActiveRecord> Collection<T> getActiveRecords(Context context, Class<T> type, Cursor cur) {

		Collection<T> records = new HashSet<T>();

		if (!cur.moveToFirst()) {
			return null;
		}

		// for each entity in the cursor
		do {

			// get the entity from the entity cache to prevent duplicate objects from being created
			T entity = ActiveRecordCache.get(type, cur.getLong(cur.getColumnIndex("id")));

			if (entity == null) {
				try {
					Constructor<T> cons = type.getConstructor(Context.class);

					if (!cons.isAccessible()) {
						cons.setAccessible(true);
					}

					entity = cons.newInstance(context);
				} catch (InstantiationException e) {
					throw new ActiveRecordException("Problem creating entity of type : " + type.getName(), e);
				} catch (IllegalAccessException e) {
					throw new ActiveRecordException("Problem creating entity of type : " + type.getName(), e);
				} catch (NoSuchMethodException e) {
					throw new ActiveRecordException("Problem creating entity of type : " + type.getName(), e);
				} catch (InvocationTargetException e) {
					throw new ActiveRecordException("Problem creating entity of type : " + type.getName(), e);
				}
			}

			setValues(type, entity, cur);

			cur.close();

			// cache this entity
			ActiveRecordCache.put(type, entity);

			records.add(entity);
		} while (cur.moveToNext());

		return records;
	}

	private static <T extends ActiveRecord> ContentValues getValues(Class<T> type, T entity) {
		ContentValues values = new ContentValues();

		for (Field field : getFields(type)) {
			String columnName = DatabaseHelper.getColumnName(field);

			if (columnName != null) {
				// don't insert/update the id because its a primary key
				if (columnName.equals("id"))
					continue;

				values.put(columnName, getFieldValue(field, entity));
			}
		}
		return values;
	}

	/**
	 * Set the values of the entity with data in the cursor.
	 *
	 * @param type   the active record class of the entities returned from the query
	 * @param entity the active record object to set
	 * @param cur	the database cursor that contains the data
	 * @param <T>    any class that extends from the ActiveRecord class
	 */
	private static <T extends ActiveRecord> void setValues(Class<T> type, T entity, Cursor cur) {

		// do the id first
		entity.id = cur.getLong(cur.getColumnIndex("id"));

		// set all the fields in this entity
		for (Field field : getFields(type)) {

			Class fieldType = field.getType();

			if (!field.isAccessible()) {
				field.setAccessible(true);
			}

			String columnName = DatabaseHelper.getColumnName(field);

			if (columnName == null) // skip ignored fields
				continue;

			int index = cur.getColumnIndex(columnName);

			if (index < 0)
				throw new ActiveRecordException("Column not returned from database, field : " + field.getName());

			try {
				if (cur.isNull(index)) {
					if (fieldType.isPrimitive()) {
						throw new ActiveRecordException("Database has null value for primitive field : " + field.getName());
					}

					field.set(entity, null);
				} else {
					// not null

					if (fieldType == Integer.TYPE || fieldType == Integer.class) {
						field.setInt(entity, cur.getInt(index));
					} else if (fieldType == Long.TYPE || fieldType == Long.class) {
						field.setLong(entity, cur.getLong(index));
					} else if (fieldType == Short.TYPE || fieldType == Short.class) {
						field.setShort(entity, cur.getShort(index));
					} else if (fieldType == Float.TYPE || fieldType == Float.class) {
						field.setFloat(entity, cur.getFloat(index));
					} else if (fieldType == Double.TYPE || fieldType == Double.class) {
						field.setDouble(entity, cur.getDouble(index));
					} else if (fieldType == Character.TYPE || fieldType == Character.class) {
//						field.setChar(entity, cur.getBlob(index));
					} else if (fieldType == Byte.TYPE || fieldType == Byte.class) {
//						field.setByte(entity, cur.getBlob(index));
					} else if (fieldType == Boolean.TYPE || fieldType == Boolean.class) {
						field.setBoolean(entity, cur.getInt(index) != 0); // non-zero for true, zero for false
					} else if (fieldType == String.class) {
						field.set(entity, cur.getString(index));
					} else if (fieldType == Date.class) {
						field.set(entity, new Date(cur.getLong(index)));
					} else if (fieldType.isArray()) {
						// TODO: implement arrays
						Log.w(TAG, "TODO: implement arrays");
					} else if (Collection.class.isInstance(fieldType)) {
						// TODO: implement collection (same as array?)
						Log.w(TAG, "TODO: implement collection (same as array?)");
					} else if (Enum.class.isAssignableFrom(fieldType)) {
						Integer enumId = cur.getInt(index);
						// TODO: implement enumerables
						Log.w(TAG, "TODO: implement enumerables");
					} else if (ActiveRecord.class.isAssignableFrom(fieldType)) {
						// TODO: load referenced entity
						Log.w(TAG, "TODO: load referenced entity");
					} else {
						Log.d(TAG, "hmm");
					}
				}
			} catch (IllegalAccessException e) {
				throw new ActiveRecordException("Could not set the value for field : " + field.getName() + " in class : " + type.getName(), e);
			}
		}
		entity._isInserted = true;
	}

	/**
	 * Recursively search the class hierarchy to find a field with the specified name.
	 *
	 * @param type	  the class to start with, it will also search all super classes
	 * @param fieldName the name of the field to find
	 * @return the field object
	 * @throws ActiveRecordException if the field was not found
	 */
	private static Field getField(Class<? extends ActiveRecord> type, String fieldName) {

		// have a quick peek in the cache before we do any reflection
		// note: if this hasn't yet been called for this class it will reflect and load the cache
		for (Field field : getFields(type)) {
			if (field.getName().equals(fieldName))
				return field;
		}

		// now look for this field, I'm guessing this should never happen
		Field field;

		try {
			field = type.getDeclaredField(fieldName);
		} catch (NoSuchFieldException e) {
			Class superClass = type.getSuperclass();
			if (superClass != null) {
				return getField(superClass, fieldName);
			} else {
				throw new ActiveRecordException("Attribute " + fieldName + " not found.", e);
			}
		}
		return field;
	}

	/**
	 * Recursively searches the class hierarchy and returns a list of fields declared.  This search is cached for each
	 * class to prevent reflecting too much.
	 *
	 * @param type the class to start with, it will also search all super classes up to ActiveRecord
	 * @return an array of fields
	 */
	static Collection<Field> getFields(Class<? extends ActiveRecord> type) {
		if (fieldCache.containsKey(type.getName()))
			return fieldCache.get(type.getName());

		ArrayList<Field> fields = new ArrayList<Field>();

		// known attributes extended from ActiveRecord
		try {
			fields.add(ActiveRecord.class.getDeclaredField("id"));
			fields.add(ActiveRecord.class.getDeclaredField("created"));
			fields.add(ActiveRecord.class.getDeclaredField("modified"));
		} catch (NoSuchFieldException e) {
			throw new ActiveRecordException("wtf is right");
		}

		for (Field field : type.getDeclaredFields()) {
			// ignore fields that are static, or abstract
			int modifiers = field.getModifiers();
			if (Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers))
				continue;

			// ignore arrays, collections, maps and enums
			if (field.getType().isArray() || Collection.class.isAssignableFrom(field.getType()) || Map.class.isAssignableFrom(field.getType()))
				continue;

			// ignore synthetic fields that are created to reference inner/outer classes
			if (field.isSynthetic())
				continue;

			// ignore fields that start with an underscore
			if (field.getName().startsWith("_") || field.getName().startsWith("m_"))
				continue;

			fields.add(field);
		}

		// search the class hierarchy until we find ActiveRecord
		Class superClass = type.getSuperclass();
		if (superClass != null && superClass != ActiveRecord.class) {
			for (Field field : getFields(superClass)) {
				fields.add(field);
			}
		}

		// put these fields into the cache
		fieldCache.put(type.getName(), fields);

		return fields;
	}

	/**
	 * Returns a string representing the value in a field.
	 *
	 * @param field  the field to use
	 * @param entity the entity which contains the field
	 * @param <T>    any class that extends from the ActiveRecord class
	 * @return a sql safe string which can be inserted into the database
	 */
	private static <T extends ActiveRecord> String getFieldValue(Field field, T entity) {

		if (!field.isAccessible()) {
			field.setAccessible(true);
		}

		Object value;
		try {
			value = field.get(entity);
		} catch (IllegalAccessException e) {
			throw new ActiveRecordException("Unable to get attribute from the entity.", e);
		}

		if (value != null) {
			Class fieldType = field.getType();

			if (fieldType == String.class)
				return "'" + value.toString() + "'";

			if (fieldType == Date.class)
				return Long.toString(((Date) value).getTime());

			if (ActiveRecord.class.isAssignableFrom(fieldType)) {
				ActiveRecord ar = (ActiveRecord) value;
				ar.save();
				return Long.toString(ar.id);
			}

			return value.toString();
		} else {
			return "NULL";
		}
	}
}